using System;
using System.IO;
using SevenZSharp.Engines;

namespace SevenZSharp
{
   /// <summary>
   /// 
   /// </summary>
   public interface ICompressionEngine
   {
      /// <summary>
      /// Gets the encoder.
      /// </summary>
      /// <value>The encoder.</value>
      ICompressionEncoder Encoder { get; set; }

      /// <summary>
      /// Gets the decoder.
      /// </summary>
      /// <value>The decoder.</value>
      ICompressionDecoder Decoder { get; set; }
   }

   /// <summary>
   /// 
   /// </summary>
   public abstract class CompressionEngine : ICompressionEngine
   {
      /// <summary>
      /// 
      /// </summary>
      public const string AssemblyVersion = "1.0.3.0";

      private static readonly ICompressionEngine s_currentEngine;

      private ICompressionDecoder m_decoder;
      private ICompressionEncoder m_encoder;

      static CompressionEngine()
      {
         s_currentEngine = new DelegationEngine();
      }

      /// <summary>
      /// 
      /// </summary>
      public CompressionEngine()
      {
         Initialize();
      }

      /// <summary>
      /// 
      /// </summary>
      public static ICompressionEngine Current
      {
         get { return s_currentEngine; }
      }

      #region ICompressionEngine Members

      /// <summary>
      /// Gets the encoder.
      /// </summary>
      /// <value>The encoder.</value>
      public virtual ICompressionEncoder Encoder
      {
         get { return m_encoder; }
         set { m_encoder = value; }
      }

      /// <summary>
      /// Gets the decoder.
      /// </summary>
      /// <value>The decoder.</value>
      public virtual ICompressionDecoder Decoder
      {
         get { return m_decoder; }
         set { m_decoder = value; }
      }

      #endregion

      /// <summary>
      /// 
      /// </summary>
      protected abstract void Initialize();

      /// <summary>
      /// Infers the compression format. If the format cannot be inferred,
      /// an exception is thrown. The alternative method GetCompressionType
      /// does not throw an exception.
      /// </summary>
      /// <param name="fileName">Name of the file.</param>
      /// <returns></returns>
      public static CompressionFormat InferCompressionFormat(string fileName)
      {
         CompressionFormat? result = GetCompressionFormat(fileName);

         if (result != null)
         {
            return result.Value;
         }
         else
         {
            throw new ArgumentException("Could not infer Compression Format from fileName " + fileName +
                                        ". File name is expected to end with the well-known extension name of the compression method.");
         }
      }

      /// <summary>
      /// Infers the compression format. If the format cannot be inferred,
      /// null is returned.
      /// </summary>
      /// <param name="fileName"></param>
      /// <returns></returns>
      public static CompressionFormat? GetCompressionFormat(string fileName)
      {
         CompressionFormat? result = null;
         string extension = Path.GetExtension(fileName);

         if (!string.IsNullOrEmpty(extension))
         {
            extension = extension.ToLower().Substring(1);
            switch (extension)
            {
               case "7z":
                  result = CompressionFormat.SevenZ;
                  break;
               case "zip":
                  result = CompressionFormat.Zip;
                  break;
               case "z":
                  result = CompressionFormat.Z;
                  break;
               case "tar":
                  result = CompressionFormat.Tar;
                  break;
               case "rar":
                  result = CompressionFormat.Rar;
                  break;
               case "rpm":
                  result = CompressionFormat.Rpm;
                  break;
               case "gz":
               case "gzip":
                  result = CompressionFormat.Gzip;
                  break;
               case "cab":
                  result = CompressionFormat.Cab;
                  break;
               case "chm":
                  result = CompressionFormat.Chm;
                  break;
               case "arj":
                  result = CompressionFormat.Arj;
                  break;
               case "bz":
               case "bz2":
                  result = CompressionFormat.Bzip2;
                  break;
               case "cpio":
                  result = CompressionFormat.Cpio;
                  break;
               case "deb":
                  result = CompressionFormat.Deb;
                  break;
               case "lzh":
                  result = CompressionFormat.Lzh;
                  break;
               case "001":
                  result = CompressionFormat.Split;
                  break;
               case "iso":
                  result = CompressionFormat.Iso;
                  break;
               case "nsis":
                  result = CompressionFormat.Nsis;
                  break;
            }
         }

         return result;
      }

      /// <summary>
      /// Gets the file extension for the specific compression <paramref name="format"/>.
      /// The extension does not begin with a period.
      /// </summary>
      /// <param name="format"></param>
      /// <returns></returns>
      public static string GetCompressionExtension(CompressionFormat format)
      {
         string result = null;

         switch (format)
         {
            case CompressionFormat.SevenZ:
               result = "7z";
               break;
            case CompressionFormat.Zip:
               result = "zip";
               break;
            case CompressionFormat.Z:
               result = "z";
               break;
            case CompressionFormat.Tar:
               result = "tar";
               break;
            case CompressionFormat.Rar:
               result = "rar";
               break;
            case CompressionFormat.Rpm:
               result = "rpm";
               break;
            case CompressionFormat.Gzip:
               result = "gzip";
               break;
            case CompressionFormat.Cab:
               result = "cab";
               break;
            case CompressionFormat.Chm:
               result = "chm";
               break;
            case CompressionFormat.Arj:
               result = "arj";
               break;
            case CompressionFormat.Bzip2:
               result = "bz";
               break;
            case CompressionFormat.Cpio:
               result = "cpio";
               break;
            case CompressionFormat.Deb:
               result = "deb";
               break;
            case CompressionFormat.Lzh:
               result = "lzh";
               break;
            case CompressionFormat.Split:
               result = "001";
               break;
            case CompressionFormat.Iso:
               result = "iso";
               break;
            case CompressionFormat.Nsis:
               result = "nsis";
               break;
            default:
               throw new NotImplementedException();
         }

         return result;
      }

      /// <summary>
      /// Determines whether <paramref name="fileName"/> is a compressed file
      /// and if this library supports executing the specified actions (as flags) on the file
      /// </summary>
      /// <param name="fileName">Name of the file.</param>
      /// <param name="action">The action.</param>
      /// <returns>
      /// 	<c>true</c> if the file is compressed and this library supported the specified encode actions on the file; otherwise, <c>false</c>.
      /// </returns>
      public static bool IsSupportedCompressedFile(string fileName, EncodeAction action)
      {
         bool result = false;

         CompressionFormat? format = GetCompressionFormat(fileName);

         if (format != null)
         {
            if ((action & EncodeAction.Decode) == EncodeAction.Decode)
            {
               // all decoders are supported in the enumeration
               result = true;
            }

            if ((action & EncodeAction.Encode) == EncodeAction.Encode)
            {
               switch (format.Value)
               {
                  case CompressionFormat.SevenZ:
                  case CompressionFormat.Gzip:
                  case CompressionFormat.Bzip2:
                  case CompressionFormat.Zip:
                  case CompressionFormat.Tar:
                     result = true;
                     break;
                  default:
                     result = false;
                     break;
               }
            }
         }

         return result;
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="sevenZExeLocation"></param>
      public static void SetOptions(string sevenZExeLocation)
      {
         if (!string.IsNullOrEmpty(sevenZExeLocation))
         {
            ShellEngine.DefaultSevenZLocation = sevenZExeLocation;
         }
      }
   }
}